# TODOs
# line 25: handle the case in which the path is hypernoded

import io
import re
import sys

# CONST CHARACTERS
SELECTOR_CHAR = "$"
LIST_MODIFIER = "+"
ATTR_MODIFIER = "a"

HYPERNODE_CHAR = "#"

#####################################
### COMMANDS AND COMMAND HANDLING ###
#####################################
class Commands:
    EventHandler, Append, InsertBefore = range(3)
    
def translateEventHandler(res, root, hyp, fur, refNodeType):
    """Translates the add event handler command"""
    
    eventname, funname = res.group(1,2)
    
    if hyp == "":
        return '__registerEventListener(%s, "%s", "%s", %s)' % (root.strip(), fur.strip(), eventname.strip(), funname.strip())
    elif fur == "":
        return '__registerEventListener(%s, "%s", "%s", %s)' % (root.strip(), hyp.strip(), eventname.strip(), funname.strip())
    else:
        return 'RegListenerWithHypernodes(%s, "%s", "%s", "%s", %s)' % (root.strip(), hyp.strip(), fur.strip(), eventname.strip(), funname.strip())

def translateAppend(res, root, hyp, fur, refNodeType):
    """Translates the append command"""
    elemName = res.group(1)
    
    if refNodeType == ATTR:
        raise ValueError("Cannot append a node to an attribute")
    
    if hyp == "":
        return 'appendChild(%s, "%s", %s)' % (root.strip(), fur.strip(), elemName.strip())
    elif fur == "":
        return '__remoteAppendChild(%s, "%s", %s)' % (root.strip(), hyp.strip(), elemName.strip())
    else:
        return 'AppendWithHypernodes(%s, "%s", "%s", %s)' % (root.strip(), hyp.strip(), fur.strip(), elemName.strip())
        
def translateInsertBefore(res, root, hyp, fur, refNodeType):
    """Translates the insertBefore command"""
    elemName = res.group(1)
    
    if refNodeType == ATTR:
        raise ValueError("Cannot insert a node into an attribute")
        
    if hyp == "":
        return 'insertBefore(%s, "%s", %s)' % (root.strip(), fur.strip(), elemName.strip())
    elif fur == "":
        return '__remoteInsertBefore(%s, "%s", %s)' % (root.strip(), hyp.strip(), elemName.strip())
    else:
        return 'InsertBeforeWithHypernodes(%s, "%s", "%s", %s)' % (root.strip(), hyp.strip(), fur.strip(), elemName.strip())

commandHandlers = {
    Commands.EventHandler : (
        re.compile("on (\S+) do (\S+)"),
        translateEventHandler
    ),
    
    Commands.Append : (
        re.compile("append (\S+)"),
        translateAppend
    ),
    
    Commands.InsertBefore : (
        re.compile("insertBefore (\S+)"),
        translateInsertBefore
    ),
}

def translateNodeCommand(command, root, hyp, fur, refNodeType):
    """
    
    """
    for cmd in commandHandlers.keys():
        res = commandHandlers[cmd][0].match(command.strip())
        if res == None:
            continue
        
        return commandHandlers[cmd][1](res, root, hyp, fur, refNodeType)
	
    raise ValueError("Command %s not recognized!" % command,)
        
        
        
        
        
########################
### LIST TRANSLATION ###
########################
def dereferenceHypernodes(path):
    """ Divides the path in two parts, one containing the path to the hypernode
    and the other containing the remaining path inside the hypernode itself.
    They are returned as a 2 element tuple.
    If there is no hypernode in the path, the ("", path) tuple is returned.
    If there is no further path, the (path_to_hyp, "") tuple is returned.
    """
    ind = path.find(HYPERNODE_CHAR)
    if ind == -1:
        return ("", path)
    
    hyperEnd = path[ind:].find("/")
    if hyperEnd == -1:
        return (path[:ind] + path[ind+1:], "")
    
    hypernodePath = path[0:ind] + path[ind+1:ind+hyperEnd]
    furtherPath = path[ind+hyperEnd:]
    
    return (hypernodePath, furtherPath)
    
# RETURN TYPES
ATOM, LIST, ATTR = range(3)

def translate(str, returnType):
    """Translate a JSplus expression containing a selector or a command on a list
    of nodes.
    """
    
    command = ""
    root = ""
    path = ""

    tmpath = ""
    
    str = str.strip()
    
    if (str.startswith("[")):
        commandEnd = str.index("]")
        command = str[1:commandEnd]
        tmpath = str[commandEnd+1:]
    else:
        tmpath = str
        
    subd = tmpath.split("->")
    assert len(subd) <= 2, "Synthax: root->path. The string %s is invalid" % (tmpath,)
    
    if len(subd) == 1:
        root = "document"
        path = tmpath
    else:
        root = subd[0]
        path = subd[1]
    
    hyp, fur = dereferenceHypernodes(path)
    
    retVal = ""
    if command == "":
        if hyp == "":
            retVal = '__selectElements(%s, "%s")' % (root.strip(), fur.strip(),)
        elif fur == "":
            retVal = '__selectElements(%s, "%s")' % (root.strip(), hyp.strip(),)
        else:
            retVal = 'DereferentiateHypernode(%s, "%s", "%s")' % (root.strip(), hyp.strip(), fur.strip())
            
        if returnType == LIST:
            return retVal
        elif returnType == ATOM:
            return "%s[0]" % (retVal,)
        elif returnType == ATTR:
            return "" #NYI
    else:
        return translateNodeCommand(command, root, hyp, fur, returnType)
        
        
        
def translateExpr(expr):
    expr = expr.strip()
    
    if expr.startswith(LIST_MODIFIER):
        return translate(expr[1:].strip(), LIST)
    elif expr.startswith(ATTR_MODIFIER):
        return translate(expr[1:].strip(), ATTR)
    else:
        return translate(expr, ATOM)
        
        
        
        
        
##############################
### SELECTOR TYPE DECODERS ###
##############################
def iterateTwo(lst):
    if (len(lst) % 2 != 0):
        raise ValueError("The list must contain an even number of elements")
    
    for i in range(0, len(lst), 2):
        val = lst[i:i+2]
        yield tuple(val)

        
        
def main():
    filelist = sys.argv[1:]
    
    for filename in filelist:
        if not filename.endswith('.jsplus'):
            continue
        
        modifiedFileName = '%s.js' % (filename[:-7],)
        
        with open(filename, mode='rt') as fileIn, open(modifiedFileName, mode="wt") as fileOut:
            inLines = fileIn.readlines()
            outLines = []
            
            for inLine in inLines:
                outLine = ''
                
                begin = inLine.find(SELECTOR_CHAR)
                
                if begin != -1:
                    end = inLine.rfind(SELECTOR_CHAR)
                
                    if end == -1:
                        raise SyntaxError("JSPlus placeholder in position %d has not a closing tag at the end of the line (line: %s)" % (begin, inLine))
                    
                    outLine += inLine[:begin]
                    outLine += translateExpr(inLine[begin+1:end])
                    outLine += inLine[end+1:]
                else:
                    outLine = inLine
                    
                outLines.append(outLine)
                
            fileOut.writelines(outLines)
            fileOut.flush()

            
            
main()
